var unminify = module.exports = {
	css: function(code, tab) {
		var defaultTab = 4,
			space = '';

		if (typeof tab == 'string')
			tab = /^\d+$/.test(tab) ? parseInt(tab, 10) : defaultTab;

		if (typeof tab == 'undefined')
			tab = defaultTab;

		if (tab < 0)
			tab = defaultTab;

		code = code
			.split('\t').join('    ')
			.replace(/\s*{\s*/g, ' {\n    ')
			.replace(/;\s*/g, ';\n    ')
			.replace(/,\s*/g, ', ')
			.replace(/[ ]*}\s*/g, '}\n')
			.replace(/\}\s*(.+)/g, '}\n$1')
			.replace(/\n    ([^:]+):\s*/g, '\n    $1: ')
			.replace(/([A-z0-9\)])}/g, '$1;\n}');

		if (tab != 4) {
			for (;tab !== 0;tab--) {
				space += ' ';
			}
			code = code.replace(/\n    /g, '\n'+space);
		}

		return code;
	},
	js: function(js_source_text, options) {
		var beautifier = new unminify.Beautifier(js_source_text, options);
		return beautifier.beautify();
	},
	Beautifier: function(js_source_text, options) {
		var input, output_lines;
		var token_text, token_type, last_type, last_last_text, indent_string;
		var flags, previous_flags, flag_store;
		var whitespace, wordchar, punct, parser_pos, line_starters, digits;
		var prefix;
		var input_wanted_newline;
		var output_wrapped, output_space_before_token;
		var input_length, n_newlines, whitespace_before_token;
		var handlers, MODE, opt;
		var preindent_string = '';

		whitespace = "\n\r\t ".split('');
		wordchar = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$'.split('');
		digits = '0123456789'.split('');

		punct = '+ - * / % & ++ -- = += -= *= /= %= == === != !== > < >= <= >> << >>> >>>= >>= <<= && &= | || ! !! , : ? ^ ^= |= ::';
		punct += ' <%= <% %> <?= <? ?>'; // try to be a good boy and try not to break the markup language identifiers
		punct = punct.split(' ');

		// words which should always start on new line.
		line_starters = 'continue,try,throw,return,var,if,switch,case,default,for,while,break,function'.split(',');

		MODE = {
			BlockStatement: 'BlockStatement', // 'BLOCK'
			Statement: 'Statement', // 'STATEMENT'
			ObjectLiteral: 'ObjectLiteral', // 'OBJECT',
			ArrayLiteral: 'ArrayLiteral', //'[EXPRESSION]',
			ForInitializer: 'ForInitializer', //'(FOR-EXPRESSION)',
			Conditional: 'Conditional', //'(COND-EXPRESSION)',
			Expression: 'Expression' //'(EXPRESSION)'
		};

		handlers = {
			'TK_START_EXPR': handle_start_expr,
			'TK_END_EXPR': handle_end_expr,
			'TK_START_BLOCK': handle_start_block,
			'TK_END_BLOCK': handle_end_block,
			'TK_WORD': handle_word,
			'TK_SEMICOLON': handle_semicolon,
			'TK_STRING': handle_string,
			'TK_EQUALS': handle_equals,
			'TK_OPERATOR': handle_operator,
			'TK_COMMA': handle_comma,
			'TK_BLOCK_COMMENT': handle_block_comment,
			'TK_INLINE_COMMENT': handle_inline_comment,
			'TK_COMMENT': handle_comment,
			'TK_DOT': handle_dot,
			'TK_UNKNOWN': handle_unknown
		};

		function create_flags(flags_base, mode) {
			var next_indent_level = 0;
			if (flags_base) {
				next_indent_level = flags_base.indentation_level;
				next_indent_level += (flags_base.var_line && flags_base.var_line_reindented) ? 1 : 0;
				if (!just_added_newline() &&
					flags_base.line_indent_level > next_indent_level) {
					next_indent_level = flags_base.line_indent_level;
				}
			}

			var next_flags = {
				mode: mode,
				parent: flags_base,
				last_text: flags_base ? flags_base.last_text : '', // last token text
				last_word: flags_base ? flags_base.last_word : '', // last 'TK_WORD' passed
				var_line: false,
				var_line_tainted: false,
				var_line_reindented: false,
				in_html_comment: false,
				multiline_frame: false,
				if_block: false,
				do_block: false,
				do_while: false,
				in_case_statement: false, // switch(..){ INSIDE HERE }
				in_case: false, // we're on the exact line with "case 0:"
				case_body: false, // the indented case-action block
				indentation_level: next_indent_level,
				line_indent_level: flags_base ? flags_base.line_indent_level : next_indent_level,
				start_line_index: output_lines.length,
				had_comment: false,
				ternary_depth: 0
			};
			return next_flags;
		}

		// Using object instead of string to allow for later expansion of info about each line

		function create_output_line() {
			return {
				text: []
			};
		}

		// Some interpreters have unexpected results with foo = baz || bar;
		options = options ? options : {};
		opt = {};

		// compatibility
		if (options.space_after_anon_function !== undefined && options.jslint_happy === undefined) {
			options.jslint_happy = options.space_after_anon_function;
		}
		if (options.braces_on_own_line !== undefined) { //graceful handling of deprecated option
			opt.brace_style = options.braces_on_own_line ? "expand" : "collapse";
		}
		opt.brace_style = options.brace_style ? options.brace_style : (opt.brace_style ? opt.brace_style : "collapse");

		// graceful handling of deprecated option
		if (opt.brace_style === "expand-strict") {
			opt.brace_style = "expand";
		}


		opt.indent_size = options.indent_size ? parseInt(options.indent_size, 10) : 4;
		opt.indent_char = options.indent_char ? options.indent_char : ' ';
		opt.preserve_newlines = (options.preserve_newlines === undefined) ? true : options.preserve_newlines;
		opt.break_chained_methods = (options.break_chained_methods === undefined) ? false : options.break_chained_methods;
		opt.max_preserve_newlines = (options.max_preserve_newlines === undefined) ? 0 : parseInt(options.max_preserve_newlines, 10);
		opt.space_in_paren = (options.space_in_paren === undefined) ? false : options.space_in_paren;
		opt.jslint_happy = (options.jslint_happy === undefined) ? false : options.jslint_happy;
		opt.keep_array_indentation = (options.keep_array_indentation === undefined) ? false : options.keep_array_indentation;
		opt.space_before_conditional = (options.space_before_conditional === undefined) ? true : options.space_before_conditional;
		opt.unescape_strings = (options.unescape_strings === undefined) ? false : options.unescape_strings;
		opt.wrap_line_length = (options.wrap_line_length === undefined) ? 0 : parseInt(options.wrap_line_length, 10);
		opt.e4x = (options.e4x === undefined) ? false : options.e4x;

		if(options.indent_with_tabs){
			opt.indent_char = '\t';
			opt.indent_size = 1;
		}

		//----------------------------------
		indent_string = '';
		while (opt.indent_size > 0) {
			indent_string += opt.indent_char;
			opt.indent_size -= 1;
		}

		while (js_source_text && (js_source_text.charAt(0) === ' ' || js_source_text.charAt(0) === '\t')) {
			preindent_string += js_source_text.charAt(0);
			js_source_text = js_source_text.substring(1);
		}
		input = js_source_text;
		// cache the source's length.
		input_length = js_source_text.length;

		last_type = 'TK_START_BLOCK'; // last token type
		last_last_text = ''; // pre-last token text
		output_lines = [create_output_line()];
		output_wrapped = false;
		output_space_before_token = false;
		whitespace_before_token = [];

		// Stack of parsing/formatting states, including MODE.
		// We tokenize, parse, and output in an almost purely a forward-only stream of token input
		// and formatted output.  This makes the beautifier less accurate than full parsers
		// but also far more tolerant of syntax errors.
		//
		// For example, the default mode is MODE.BlockStatement. If we see a '{' we push a new frame of type
		// MODE.BlockStatement on the the stack, even though it could be object literal.  If we later
		// encounter a ":", we'll switch to to MODE.ObjectLiteral.  If we then see a ";",
		// most full parsers would die, but the beautifier gracefully falls back to
		// MODE.BlockStatement and continues on.
		flag_store = [];
		set_mode(MODE.BlockStatement);

		parser_pos = 0;

		this.beautify = function() {
			/*jshint onevar:true */
			var t, i, keep_whitespace, sweet_code, line_index;

			while (true) {
				t = get_next_token();
				token_text = t[0];
				token_type = t[1];

				if (token_type === 'TK_EOF') {
					break;
				}

				keep_whitespace = opt.keep_array_indentation && is_array(flags.mode);
				input_wanted_newline = n_newlines > 0;

				if (keep_whitespace) {
					for (i = 0; i < n_newlines; i += 1) {
						print_newline(i > 0);
					}
				} else {
					if (opt.max_preserve_newlines && n_newlines > opt.max_preserve_newlines) {
						n_newlines = opt.max_preserve_newlines;
					}

					if (opt.preserve_newlines) {
						if (n_newlines > 1) {
							print_newline();
							for (i = 1; i < n_newlines; i += 1) {
								print_newline(true);
							}
						}
					}
				}

				handlers[token_type]();

				// The cleanest handling of inline comments is to treat them as though they aren't there.
				// Just continue formatting and the behavior should be logical.
				// Also ignore unknown tokens.  Again, this should result in better behavior.
				if (token_type !== 'TK_INLINE_COMMENT' && token_type !== 'TK_COMMENT' &&
					token_type !== 'TK_BLOCK_COMMENT' && token_type !== 'TK_UNKNOWN') {
					last_last_text = flags.last_text;
					last_type = token_type;
					flags.last_text = token_text;
				}
				flags.had_comment = (token_type === 'TK_INLINE_COMMENT' || token_type === 'TK_COMMENT' || token_type === 'TK_BLOCK_COMMENT');
			}


			sweet_code = output_lines[0].text.join('');
			for (line_index = 1; line_index < output_lines.length; line_index++) {
				sweet_code += '\n' + output_lines[line_index].text.join('');
			}
			sweet_code = sweet_code.replace(/[\r\n ]+$/, '');
			return sweet_code;
		};

		function trim_output(eat_newlines) {
			eat_newlines = (eat_newlines === undefined) ? false : eat_newlines;

			if (output_lines.length) {
				trim_output_line(output_lines[output_lines.length - 1], eat_newlines);

				while (eat_newlines && output_lines.length > 1 &&
					output_lines[output_lines.length - 1].text.length === 0) {
					output_lines.pop();
					trim_output_line(output_lines[output_lines.length - 1], eat_newlines);
				}
			}
		}

		function trim_output_line(line) {
			while (line.text.length &&
				(line.text[line.text.length - 1] === ' ' ||
					line.text[line.text.length - 1] === indent_string ||
					line.text[line.text.length - 1] === preindent_string)) {
				line.text.pop();
			}
		}

		function trim(s) {
			return s.replace(/^\s+|\s+$/g, '');
		}

		// we could use just string.split, but
		// IE doesn't like returning empty strings

		function split_newlines(s) {
			//return s.split(/\x0d\x0a|\x0a/);

			s = s.replace(/\x0d/g, '');
			var out = [],
				idx = s.indexOf("\n");
			while (idx !== -1) {
				out.push(s.substring(0, idx));
				s = s.substring(idx + 1);
				idx = s.indexOf("\n");
			}
			if (s.length) {
				out.push(s);
			}
			return out;
		}

		function just_added_newline() {
			var line = output_lines[output_lines.length - 1];
			return line.text.length === 0;
		}

		function just_added_blankline() {
			if (just_added_newline()) {
				if (output_lines.length === 1) {
					return true; // start of the file and newline = blank
				}

				var line = output_lines[output_lines.length - 2];
				return line.text.length === 0;
			}
			return false;
		}

		function allow_wrap_or_preserved_newline(force_linewrap) {
			force_linewrap = (force_linewrap === undefined) ? false : force_linewrap;
			if (opt.wrap_line_length && !force_linewrap) {
				var line = output_lines[output_lines.length - 1];
				var proposed_line_length = 0;
				// never wrap the first token of a line.
				if (line.text.length > 0) {
					proposed_line_length = line.text.join('').length + token_text.length +
						(output_space_before_token ? 1 : 0);
					if (proposed_line_length >= opt.wrap_line_length) {
						force_linewrap = true;
					}
				}
			}
			if (((opt.preserve_newlines && input_wanted_newline) || force_linewrap) && !just_added_newline()) {
				print_newline(false, true);

				// Expressions and array literals already indent their contents.
				if (!(is_array(flags.mode) || is_expression(flags.mode))) {
					output_wrapped = true;
				}
			}
		}

		function print_newline(force_newline, preserve_statement_flags) {
			output_wrapped = false;
			output_space_before_token = false;

			if (!preserve_statement_flags) {
				if (flags.last_text !== ';') {
					while (flags.mode === MODE.Statement && !flags.if_block && !flags.do_block) {
						restore_mode();
					}
				}
			}

			if (output_lines.length === 1 && just_added_newline()) {
				return; // no newline on start of file
			}

			if (force_newline || !just_added_newline()) {
				flags.multiline_frame = true;
				output_lines.push(create_output_line());
			}
		}

		function print_token_line_indentation() {
			if (just_added_newline()) {
				var line = output_lines[output_lines.length - 1];
				if (opt.keep_array_indentation && is_array(flags.mode) && input_wanted_newline) {
					// prevent removing of this whitespace as redundant
					line.text.push('');
					for (var i = 0; i < whitespace_before_token.length; i += 1) {
						line.text.push(whitespace_before_token[i]);
					}
				} else {
					if (preindent_string) {
						line.text.push(preindent_string);
					}

					print_indent_string(flags.indentation_level +
						(flags.var_line && flags.var_line_reindented ? 1 : 0) +
						(output_wrapped ? 1 : 0));
				}
			}
		}

		function print_indent_string(level) {
			// Never indent your first output indent at the start of the file
			if (output_lines.length > 1) {
				var line = output_lines[output_lines.length - 1];

				flags.line_indent_level = level;
				for (var i = 0; i < level; i += 1) {
					line.text.push(indent_string);
				}
			}
		}

		function print_token_space_before() {
			var line = output_lines[output_lines.length - 1];
			if (output_space_before_token && line.text.length) {
				var last_output = line.text[line.text.length - 1];
				if (last_output !== ' ' && last_output !== indent_string) { // prevent occassional duplicate space
					line.text.push(' ');
				}
			}
		}

		function print_token(printable_token) {
			printable_token = printable_token || token_text;
			print_token_line_indentation();
			output_wrapped = false;
			print_token_space_before();
			output_space_before_token = false;
			output_lines[output_lines.length - 1].text.push(printable_token);
		}

		function indent() {
			flags.indentation_level += 1;
		}

		function deindent() {
			if (flags.indentation_level > 0 &&
				((!flags.parent) || flags.indentation_level > flags.parent.indentation_level))
				flags.indentation_level -= 1;
		}

		function remove_redundant_indentation(frame) {
			// This implementation is effective but has some issues:
			//     - less than great performance due to array splicing
			//     - can cause line wrap to happen too soon due to indent removal
			//           after wrap points are calculated
			// These issues are minor compared to ugly indentation.

			if (frame.multiline_frame) return;

			// remove one indent from each line inside this section
			var index = frame.start_line_index;
			var splice_index = 0;
			var line;

			while (index < output_lines.length) {
				line = output_lines[index];
				index++;

				// skip empty lines
				if (line.text.length === 0) {
					continue;
				}

				// skip the preindent string if present
				if (preindent_string && line.text[0] === preindent_string) {
					splice_index = 1;
				} else {
					splice_index = 0;
				}

				// remove one indent, if present
				if (line.text[splice_index] === indent_string) {
					line.text.splice(splice_index, 1);
				}
			}
		}

		function set_mode(mode) {
			if (flags) {
				flag_store.push(flags);
				previous_flags = flags;
			} else {
				previous_flags = create_flags(null, mode);
			}

			flags = create_flags(previous_flags, mode);
		}

		function is_array(mode) {
			return mode === MODE.ArrayLiteral;
		}

		function is_expression(mode) {
			return in_array(mode, [MODE.Expression, MODE.ForInitializer, MODE.Conditional]);
		}

		function restore_mode() {
			if (flag_store.length > 0) {
				previous_flags = flags;
				flags = flag_store.pop();
			}
		}

		function start_of_object_property() {
			return flags.mode === MODE.ObjectLiteral && flags.last_text === ':' &&
				flags.ternary_depth === 0;
		}

		function start_of_statement() {
			if (
				(flags.last_text === 'do' ||
					(flags.last_text === 'else' && token_text !== 'if') ||
					(last_type === 'TK_END_EXPR' && (previous_flags.mode === MODE.ForInitializer || previous_flags.mode === MODE.Conditional)))) {
				// Issue #276:
				// If starting a new statement with [if, for, while, do], push to a new line.
				// if (a) if (b) if(c) d(); else e(); else f();
				allow_wrap_or_preserved_newline(
					in_array(token_text, ['do', 'for', 'if', 'while']));

				set_mode(MODE.Statement);
				// Issue #275:
				// If starting on a newline, all of a statement should be indented.
				// if not, use line wrapping logic for indent.
				if (just_added_newline()) {
					indent();
					output_wrapped = false;
				}
				return true;
			}
			return false;
		}

		function all_lines_start_with(lines, c) {
			for (var i = 0; i < lines.length; i++) {
				var line = trim(lines[i]);
				if (line.charAt(0) !== c) {
					return false;
				}
			}
			return true;
		}

		function is_special_word(word) {
			return in_array(word, ['case', 'return', 'do', 'if', 'throw', 'else']);
		}

		function in_array(what, arr) {
			for (var i = 0; i < arr.length; i += 1) {
				if (arr[i] === what) {
					return true;
				}
			}
			return false;
		}

		function unescape_string(s) {
			var esc = false,
				out = '',
				pos = 0,
				s_hex = '',
				escaped = 0,
				c;

			while (esc || pos < s.length) {

				c = s.charAt(pos);
				pos++;

				if (esc) {
					esc = false;
					if (c === 'x') {
						// simple hex-escape \x24
						s_hex = s.substr(pos, 2);
						pos += 2;
					} else if (c === 'u') {
						// unicode-escape, \u2134
						s_hex = s.substr(pos, 4);
						pos += 4;
					} else {
						// some common escape, e.g \n
						out += '\\' + c;
						continue;
					}
					if (!s_hex.match(/^[0123456789abcdefABCDEF]+$/)) {
						// some weird escaping, bail out,
						// leaving whole string intact
						return s;
					}

					escaped = parseInt(s_hex, 16);

					if (escaped >= 0x00 && escaped < 0x20) {
						// leave 0x00...0x1f escaped
						if (c === 'x') {
							out += '\\x' + s_hex;
						} else {
							out += '\\u' + s_hex;
						}
						continue;
					} else if (escaped === 0x22 || escaped === 0x27 || escaped === 0x5c) {
						// single-quote, apostrophe, backslash - escape these
						out += '\\' + String.fromCharCode(escaped);
					} else if (c === 'x' && escaped > 0x7e && escaped <= 0xff) {
						// we bail out on \x7f..\xff,
						// leaving whole string escaped,
						// as it's probably completely binary
						return s;
					} else {
						out += String.fromCharCode(escaped);
					}
				} else if (c === '\\') {
					esc = true;
				} else {
					out += c;
				}
			}
			return out;
		}

		function is_next(find) {
			var local_pos = parser_pos;
			var c = input.charAt(local_pos);
			while (in_array(c, whitespace) && c !== find) {
				local_pos++;
				if (local_pos >= input_length) {
					return false;
				}
				c = input.charAt(local_pos);
			}
			return c === find;
		}

		function get_next_token() {
			var i, resulting_string;

			n_newlines = 0;

			if (parser_pos >= input_length) {
				return ['', 'TK_EOF'];
			}

			input_wanted_newline = false;
			whitespace_before_token = [];

			var c = input.charAt(parser_pos);
			parser_pos += 1;

			while (in_array(c, whitespace)) {

				if (c === '\n') {
					n_newlines += 1;
					whitespace_before_token = [];
				} else if (n_newlines) {
					if (c === indent_string) {
						whitespace_before_token.push(indent_string);
					} else if (c !== '\r') {
						whitespace_before_token.push(' ');
					}
				}

				if (parser_pos >= input_length) {
					return ['', 'TK_EOF'];
				}

				c = input.charAt(parser_pos);
				parser_pos += 1;
			}

			if (in_array(c, wordchar)) {
				if (parser_pos < input_length) {
					while (in_array(input.charAt(parser_pos), wordchar)) {
						c += input.charAt(parser_pos);
						parser_pos += 1;
						if (parser_pos === input_length) {
							break;
						}
					}
				}

				// small and surprisingly unugly hack for 1E-10 representation
				if (parser_pos !== input_length && c.match(/^[0-9]+[Ee]$/) && (input.charAt(parser_pos) === '-' || input.charAt(parser_pos) === '+')) {

					var sign = input.charAt(parser_pos);
					parser_pos += 1;

					var t = get_next_token();
					c += sign + t[0];
					return [c, 'TK_WORD'];
				}

				if (c === 'in') { // hack for 'in' operator
					return [c, 'TK_OPERATOR'];
				}
				return [c, 'TK_WORD'];
			}

			if (c === '(' || c === '[') {
				return [c, 'TK_START_EXPR'];
			}

			if (c === ')' || c === ']') {
				return [c, 'TK_END_EXPR'];
			}

			if (c === '{') {
				return [c, 'TK_START_BLOCK'];
			}

			if (c === '}') {
				return [c, 'TK_END_BLOCK'];
			}

			if (c === ';') {
				return [c, 'TK_SEMICOLON'];
			}

			if (c === '/') {
				var comment = '';
				// peek for comment /* ... */
				var inline_comment = true;
				if (input.charAt(parser_pos) === '*') {
					parser_pos += 1;
					if (parser_pos < input_length) {
						while (parser_pos < input_length && !(input.charAt(parser_pos) === '*' && input.charAt(parser_pos + 1) && input.charAt(parser_pos + 1) === '/')) {
							c = input.charAt(parser_pos);
							comment += c;
							if (c === "\n" || c === "\r") {
								inline_comment = false;
							}
							parser_pos += 1;
							if (parser_pos >= input_length) {
								break;
							}
						}
					}
					parser_pos += 2;
					if (inline_comment && n_newlines === 0) {
						return ['/*' + comment + '*/', 'TK_INLINE_COMMENT'];
					} else {
						return ['/*' + comment + '*/', 'TK_BLOCK_COMMENT'];
					}
				}
				// peek for comment // ...
				if (input.charAt(parser_pos) === '/') {
					comment = c;
					while (input.charAt(parser_pos) !== '\r' && input.charAt(parser_pos) !== '\n') {
						comment += input.charAt(parser_pos);
						parser_pos += 1;
						if (parser_pos >= input_length) {
							break;
						}
					}
					return [comment, 'TK_COMMENT'];
				}

			}


			if (c === "'" || c === '"' || // string
				(
					(c === '/') || // regexp
					(opt.e4x && c === "<" && input.slice(parser_pos - 1).match(/^<([-a-zA-Z:0-9_.]+|{[^{}]*}|!\[CDATA\[[\s\S]*?\]\])\s*([-a-zA-Z:0-9_.]+=('[^']*'|"[^"]*"|{[^{}]*})\s*)*\/?\s*>/)) // xml
				) && ( // regex and xml can only appear in specific locations during parsing
					(last_type === 'TK_WORD' && is_special_word(flags.last_text)) ||
					(last_type === 'TK_END_EXPR' && in_array(previous_flags.mode, [MODE.Conditional, MODE.ForInitializer])) ||
					(in_array(last_type, ['TK_COMMENT', 'TK_START_EXPR', 'TK_START_BLOCK',
						'TK_END_BLOCK', 'TK_OPERATOR', 'TK_EQUALS', 'TK_EOF', 'TK_SEMICOLON', 'TK_COMMA'
					]))
				)) {

				var sep = c,
					esc = false,
					has_char_escapes = false;

				resulting_string = c;

				if (parser_pos < input_length) {
					if (sep === '/') {
						//
						// handle regexp
						//
						var in_char_class = false;
						while (esc || in_char_class || input.charAt(parser_pos) !== sep) {
							resulting_string += input.charAt(parser_pos);
							if (!esc) {
								esc = input.charAt(parser_pos) === '\\';
								if (input.charAt(parser_pos) === '[') {
									in_char_class = true;
								} else if (input.charAt(parser_pos) === ']') {
									in_char_class = false;
								}
							} else {
								esc = false;
							}
							parser_pos += 1;
							if (parser_pos >= input_length) {
								// incomplete string/rexp when end-of-file reached.
								// bail out with what had been received so far.
								return [resulting_string, 'TK_STRING'];
							}
						}
					} else if (opt.e4x && sep === '<') {
						//
						// handle e4x xml literals
						//
						var xmlRegExp = /<(\/?)([-a-zA-Z:0-9_.]+|{[^{}]*}|!\[CDATA\[[\s\S]*?\]\])\s*([-a-zA-Z:0-9_.]+=('[^']*'|"[^"]*"|{[^{}]*})\s*)*(\/?)\s*>/g;
						var xmlStr = input.slice(parser_pos - 1);
						var match = xmlRegExp.exec(xmlStr);
						if (match && match.index === 0) {
							var rootTag = match[2];
							var depth = 0;
							while (match) {
								var isEndTag = !! match[1];
								var tagName = match[2];
								var isSingletonTag = ( !! match[match.length - 1]) || (tagName.slice(0, 8) === "![CDATA[");
								if (tagName === rootTag && !isSingletonTag) {
									if (isEndTag) {
										--depth;
									} else {
										++depth;
									}
								}
								if (depth <= 0) {
									break;
								}
								match = xmlRegExp.exec(xmlStr);
							}
							var xmlLength = match ? match.index + match[0].length : xmlStr.length;
							parser_pos += xmlLength - 1;
							return [xmlStr.slice(0, xmlLength), "TK_STRING"];
						}
					} else {
						//
						// handle string
						//
						while (esc || input.charAt(parser_pos) !== sep) {
							resulting_string += input.charAt(parser_pos);
							if (esc) {
								if (input.charAt(parser_pos) === 'x' || input.charAt(parser_pos) === 'u') {
									has_char_escapes = true;
								}
								esc = false;
							} else {
								esc = input.charAt(parser_pos) === '\\';
							}
							parser_pos += 1;
							if (parser_pos >= input_length) {
								// incomplete string/rexp when end-of-file reached.
								// bail out with what had been received so far.
								return [resulting_string, 'TK_STRING'];
							}
						}

					}
				}

				parser_pos += 1;
				resulting_string += sep;

				if (has_char_escapes && opt.unescape_strings) {
					resulting_string = unescape_string(resulting_string);
				}

				if (sep === '/') {
					// regexps may have modifiers /regexp/MOD , so fetch those, too
					while (parser_pos < input_length && in_array(input.charAt(parser_pos), wordchar)) {
						resulting_string += input.charAt(parser_pos);
						parser_pos += 1;
					}
				}
				return [resulting_string, 'TK_STRING'];
			}

			if (c === '#') {


				if (output_lines.length === 1 && output_lines[0].text.length === 0 &&
					input.charAt(parser_pos) === '!') {
					// shebang
					resulting_string = c;
					while (parser_pos < input_length && c !== '\n') {
						c = input.charAt(parser_pos);
						resulting_string += c;
						parser_pos += 1;
					}
					return [trim(resulting_string) + '\n', 'TK_UNKNOWN'];
				}



				// Spidermonkey-specific sharp variables for circular references
				// https://developer.mozilla.org/En/Sharp_variables_in_JavaScript
				// http://mxr.mozilla.org/mozilla-central/source/js/src/jsscan.cpp around line 1935
				var sharp = '#';
				if (parser_pos < input_length && in_array(input.charAt(parser_pos), digits)) {
					do {
						c = input.charAt(parser_pos);
						sharp += c;
						parser_pos += 1;
					} while (parser_pos < input_length && c !== '#' && c !== '=');
					if (c === '#') {
						//
					} else if (input.charAt(parser_pos) === '[' && input.charAt(parser_pos + 1) === ']') {
						sharp += '[]';
						parser_pos += 2;
					} else if (input.charAt(parser_pos) === '{' && input.charAt(parser_pos + 1) === '}') {
						sharp += '{}';
						parser_pos += 2;
					}
					return [sharp, 'TK_WORD'];
				}
			}

			if (c === '<' && input.substring(parser_pos - 1, parser_pos + 3) === '<!--') {
				parser_pos += 3;
				c = '<!--';
				while (input.charAt(parser_pos) !== '\n' && parser_pos < input_length) {
					c += input.charAt(parser_pos);
					parser_pos++;
				}
				flags.in_html_comment = true;
				return [c, 'TK_COMMENT'];
			}

			if (c === '-' && flags.in_html_comment && input.substring(parser_pos - 1, parser_pos + 2) === '-->') {
				flags.in_html_comment = false;
				parser_pos += 2;
				return ['-->', 'TK_COMMENT'];
			}

			if (c === '.') {
				return [c, 'TK_DOT'];
			}

			if (in_array(c, punct)) {
				while (parser_pos < input_length && in_array(c + input.charAt(parser_pos), punct)) {
					c += input.charAt(parser_pos);
					parser_pos += 1;
					if (parser_pos >= input_length) {
						break;
					}
				}

				if (c === ',') {
					return [c, 'TK_COMMA'];
				} else if (c === '=') {
					return [c, 'TK_EQUALS'];
				} else {
					return [c, 'TK_OPERATOR'];
				}
			}

			return [c, 'TK_UNKNOWN'];
		}

		function handle_start_expr() {
			if (start_of_statement()) {
				// The conditional starts the statement if appropriate.
			}

			var next_mode = MODE.Expression;
			if (token_text === '[') {

				if (last_type === 'TK_WORD' || flags.last_text === ')') {
					// this is array index specifier, break immediately
					// a[x], fn()[x]
					if (in_array(flags.last_text, line_starters)) {
						output_space_before_token = true;
					}
					set_mode(next_mode);
					print_token();
					indent();
					if (opt.space_in_paren) {
						output_space_before_token = true;
					}
					return;
				}

				next_mode = MODE.ArrayLiteral;
				if (is_array(flags.mode)) {
					if (flags.last_text === '[' ||
						(flags.last_text === ',' && (last_last_text === ']' || last_last_text === '}'))) {
						// ], [ goes to new line
						// }, [ goes to new line
						if (!opt.keep_array_indentation) {
							print_newline();
						}
					}
				}

			} else {
				if (flags.last_text === 'for') {
					next_mode = MODE.ForInitializer;
				} else if (in_array(flags.last_text, ['if', 'while'])) {
					next_mode = MODE.Conditional;
				} else {
					// next_mode = MODE.Expression;
				}
			}

			if (flags.last_text === ';' || last_type === 'TK_START_BLOCK') {
				print_newline();
			} else if (last_type === 'TK_END_EXPR' || last_type === 'TK_START_EXPR' || last_type === 'TK_END_BLOCK' || flags.last_text === '.') {
				// TODO: Consider whether forcing this is required.  Review failing tests when removed.
				allow_wrap_or_preserved_newline(input_wanted_newline);
				output_wrapped = false;
				// do nothing on (( and )( and ][ and ]( and .(
			} else if (last_type !== 'TK_WORD' && last_type !== 'TK_OPERATOR') {
				output_space_before_token = true;
			} else if (flags.last_word === 'function' || flags.last_word === 'typeof') {
				// function() vs function ()
				if (opt.jslint_happy) {
					output_space_before_token = true;
				}
			} else if (in_array(flags.last_text, line_starters) || flags.last_text === 'catch') {
				if (opt.space_before_conditional) {
					output_space_before_token = true;
				}
			}

			// Support of this kind of newline preservation.
			// a = (b &&
			//     (c || d));
			if (token_text === '(') {
				if (last_type === 'TK_EQUALS' || last_type === 'TK_OPERATOR') {
					if (!start_of_object_property()) {
						allow_wrap_or_preserved_newline();
					}
				}
			}

			set_mode(next_mode);
			print_token();
			if (opt.space_in_paren) {
				output_space_before_token = true;
			}

			// In all cases, if we newline while inside an expression it should be indented.
			indent();
		}

		function handle_end_expr() {
			// statements inside expressions are not valid syntax, but...
			// statements must all be closed when their container closes
			while (flags.mode === MODE.Statement) {
				restore_mode();
			}

			if (token_text === ']' && is_array(flags.mode) && flags.multiline_frame && !opt.keep_array_indentation) {
				print_newline();
			}

			if (flags.multiline_frame) {
				allow_wrap_or_preserved_newline();
			}
			if (opt.space_in_paren) {
				if (last_type === 'TK_START_EXPR') {
					// () [] no inner space in empty parens like these, ever, ref #320
					trim_output();
					output_space_before_token = false;
				} else {
					output_space_before_token = true;
				}
			}
			if (token_text === ']' && opt.keep_array_indentation) {
				print_token();
				restore_mode();
			} else {
				restore_mode();
				print_token();
			}
			remove_redundant_indentation(previous_flags);

			// do {} while () // no statement required after
			if (flags.do_while && previous_flags.mode === MODE.Conditional) {
				previous_flags.mode = MODE.Expression;
				flags.do_block = false;
				flags.do_while = false;

			}
		}

		function handle_start_block() {
			set_mode(MODE.BlockStatement);

			var empty_braces = is_next('}');
			var empty_anonymous_function = empty_braces && flags.last_word === 'function' &&
				last_type === 'TK_END_EXPR';

			if (opt.brace_style === "expand") {
				if (last_type !== 'TK_OPERATOR' &&
					(empty_anonymous_function ||
						last_type === 'TK_EQUALS' ||
						(is_special_word(flags.last_text) && flags.last_text !== 'else'))) {
					output_space_before_token = true;
				} else {
					print_newline();
				}
			} else { // collapse
				if (last_type !== 'TK_OPERATOR' && last_type !== 'TK_START_EXPR') {
					if (last_type === 'TK_START_BLOCK') {
						print_newline();
					} else {
						output_space_before_token = true;
					}
				} else {
					// if TK_OPERATOR or TK_START_EXPR
					if (is_array(previous_flags.mode) && flags.last_text === ',') {
						if (last_last_text === '}') {
							// }, { in array context
							output_space_before_token = true;
						} else {
							print_newline(); // [a, b, c, {
						}
					}
				}
			}
			print_token();
			indent();
		}

		function handle_end_block() {
			// statements must all be closed when their container closes
			while (flags.mode === MODE.Statement) {
				restore_mode();
			}
			var empty_braces = last_type === 'TK_START_BLOCK';

			if (opt.brace_style === "expand") {
				if (!empty_braces) {
					print_newline();
				}
			} else {
				// skip {}
				if (!empty_braces) {
					if (is_array(flags.mode) && opt.keep_array_indentation) {
						// we REALLY need a newline here, but newliner would skip that
						opt.keep_array_indentation = false;
						print_newline();
						opt.keep_array_indentation = true;

					} else {
						print_newline();
					}
				}
			}
			restore_mode();
			print_token();
		}

		function handle_word() {
			if (start_of_statement()) {
				// The conditional starts the statement if appropriate.
			} else if (input_wanted_newline && !is_expression(flags.mode) &&
				(last_type !== 'TK_OPERATOR' || (flags.last_text === '--' || flags.last_text === '++')) &&
				last_type !== 'TK_EQUALS' &&
				(opt.preserve_newlines || flags.last_text !== 'var')) {

				print_newline();
			}

			if (flags.do_block && !flags.do_while) {
				if (token_text === 'while') {
					// do {} ## while ()
					output_space_before_token = true;
					print_token();
					output_space_before_token = true;
					flags.do_while = true;
					return;
				} else {
					// do {} should always have while as the next word.
					// if we don't see the expected while, recover
					print_newline();
					flags.do_block = false;
				}
			}

			// if may be followed by else, or not
			// Bare/inline ifs are tricky
			// Need to unwind the modes correctly: if (a) if (b) c(); else d(); else e();
			if (flags.if_block) {
				if (token_text !== 'else') {
					while (flags.mode === MODE.Statement) {
						restore_mode();
					}
					flags.if_block = false;
				}
			}

			if (token_text === 'case' || (token_text === 'default' && flags.in_case_statement)) {
				print_newline();
				if (flags.case_body || opt.jslint_happy) {
					// switch cases following one another
					deindent();
					flags.case_body = false;
				}
				print_token();
				flags.in_case = true;
				flags.in_case_statement = true;
				return;
			}

			if (token_text === 'function') {
				if (flags.var_line && last_type !== 'TK_EQUALS') {
					flags.var_line_reindented = true;
				}
				if (in_array(flags.last_text, ['}', ';']) || (just_added_newline() && ! in_array(flags.last_text, ['{', ':', '=', ',']))) {
					// make sure there is a nice clean space of at least one blank line
					// before a new function definition
					if ( ! just_added_blankline() && ! flags.had_comment) {
						print_newline();
						print_newline(true);
					}
				}
				if (last_type === 'TK_WORD') {
					if (flags.last_text === 'get' || flags.last_text === 'set' || flags.last_text === 'new' || flags.last_text === 'return') {
						output_space_before_token = true;
					} else {
						print_newline();
					}
				} else if (last_type === 'TK_OPERATOR' || flags.last_text === '=') {
					// foo = function
					output_space_before_token = true;
				} else if (is_expression(flags.mode)) {
					// (function
				} else {
					print_newline();
				}
			}

			if (last_type === 'TK_COMMA' || last_type === 'TK_START_EXPR' || last_type === 'TK_EQUALS' || last_type === 'TK_OPERATOR') {
				if (!start_of_object_property()) {
					allow_wrap_or_preserved_newline();
				}
			}

			if (token_text === 'function') {
				print_token();
				flags.last_word = token_text;
				return;
			}

			prefix = 'NONE';

			if (last_type === 'TK_END_BLOCK') {
				if (!in_array(token_text, ['else', 'catch', 'finally'])) {
					prefix = 'NEWLINE';
				} else {
					if (opt.brace_style === "expand" || opt.brace_style === "end-expand") {
						prefix = 'NEWLINE';
					} else {
						prefix = 'SPACE';
						output_space_before_token = true;
					}
				}
			} else if (last_type === 'TK_SEMICOLON' && flags.mode === MODE.BlockStatement) {
				// TODO: Should this be for STATEMENT as well?
				prefix = 'NEWLINE';
			} else if (last_type === 'TK_SEMICOLON' && is_expression(flags.mode)) {
				prefix = 'SPACE';
			} else if (last_type === 'TK_STRING') {
				prefix = 'NEWLINE';
			} else if (last_type === 'TK_WORD') {
				prefix = 'SPACE';
			} else if (last_type === 'TK_START_BLOCK') {
				prefix = 'NEWLINE';
			} else if (last_type === 'TK_END_EXPR') {
				output_space_before_token = true;
				prefix = 'NEWLINE';
			}

			if (in_array(token_text, line_starters) && flags.last_text !== ')') {
				if (flags.last_text === 'else') {
					prefix = 'SPACE';
				} else {
					prefix = 'NEWLINE';
				}

			}

			if (in_array(token_text, ['else', 'catch', 'finally'])) {
				if (last_type !== 'TK_END_BLOCK' || opt.brace_style === "expand" || opt.brace_style === "end-expand") {
					print_newline();
				} else {
					trim_output(true);
					var line = output_lines[output_lines.length - 1];
					// If we trimmed and there's something other than a close block before us
					// put a newline back in.  Handles '} // comment' scenario.
					if (line.text[line.text.length - 1] !== '}') {
						print_newline();
					}
					output_space_before_token = true;
				}
			} else if (prefix === 'NEWLINE') {
				if (is_special_word(flags.last_text)) {
					// no newline between 'return nnn'
					output_space_before_token = true;
				} else if (last_type !== 'TK_END_EXPR') {
					if ((last_type !== 'TK_START_EXPR' || token_text !== 'var') && flags.last_text !== ':') {
						// no need to force newline on 'var': for (var x = 0...)
						if (token_text === 'if' && flags.last_word === 'else' && flags.last_text !== '{') {
							// no newline for } else if {
							output_space_before_token = true;
						} else {
							flags.var_line = false;
							flags.var_line_reindented = false;
							print_newline();
						}
					}
				} else if (in_array(token_text, line_starters) && flags.last_text !== ')') {
					flags.var_line = false;
					flags.var_line_reindented = false;
					print_newline();
				}
			} else if (is_array(flags.mode) && flags.last_text === ',' && last_last_text === '}') {
				print_newline(); // }, in lists get a newline treatment
			} else if (prefix === 'SPACE') {
				output_space_before_token = true;
			}
			print_token();
			flags.last_word = token_text;

			if (token_text === 'var') {
				flags.var_line = true;
				flags.var_line_reindented = false;
				flags.var_line_tainted = false;
			}

			if (token_text === 'do') {
				flags.do_block = true;
			}

			if (token_text === 'if') {
				flags.if_block = true;
			}
		}

		function handle_semicolon() {
			if (start_of_statement()) {
				// The conditional starts the statement if appropriate.
				// Semicolon can be the start (and end) of a statement
				output_space_before_token = false;
			}
			while (flags.mode === MODE.Statement && !flags.if_block && !flags.do_block) {
				restore_mode();
			}
			print_token();
			flags.var_line = false;
			flags.var_line_reindented = false;
			if (flags.mode === MODE.ObjectLiteral) {
				// if we're in OBJECT mode and see a semicolon, its invalid syntax
				// recover back to treating this as a BLOCK
				flags.mode = MODE.BlockStatement;
			}
		}

		function handle_string() {
			if (start_of_statement()) {
				// The conditional starts the statement if appropriate.
				// One difference - strings want at least a space before
				output_space_before_token = true;
			} else if (last_type === 'TK_WORD') {
				output_space_before_token = true;
			} else if (last_type === 'TK_COMMA' || last_type === 'TK_START_EXPR' || last_type === 'TK_EQUALS' || last_type === 'TK_OPERATOR') {
				if (!start_of_object_property()) {
					allow_wrap_or_preserved_newline();
				}
			} else {
				print_newline();
			}
			print_token();
		}

		function handle_equals() {
			if (flags.var_line) {
				// just got an '=' in a var-line, different formatting/line-breaking, etc will now be done
				flags.var_line_tainted = true;
			}
			output_space_before_token = true;
			print_token();
			output_space_before_token = true;
		}

		function handle_comma() {
			if (flags.var_line) {
				if (is_expression(flags.mode) || last_type === 'TK_END_BLOCK') {
					// do not break on comma, for(var a = 1, b = 2)
					flags.var_line_tainted = false;
				}

				if (flags.var_line) {
					flags.var_line_reindented = true;
				}

				print_token();

				if (flags.var_line_tainted) {
					flags.var_line_tainted = false;
					print_newline();
				} else {
					output_space_before_token = true;
				}
				return;
			}

			if (last_type === 'TK_END_BLOCK' && flags.mode !== MODE.Expression) {
				print_token();
				if (flags.mode === MODE.ObjectLiteral && flags.last_text === '}') {
					print_newline();
				} else {
					output_space_before_token = true;
				}
			} else {
				if (flags.mode === MODE.ObjectLiteral) {
					print_token();
					print_newline();
				} else {
					// EXPR or DO_BLOCK
					print_token();
					output_space_before_token = true;
				}
			}
		}

		function handle_operator() {
			var space_before = true;
			var space_after = true;
			if (is_special_word(flags.last_text)) {
				// "return" had a special handling in TK_WORD. Now we need to return the favor
				output_space_before_token = true;
				print_token();
				return;
			}

			// hack for actionscript's import .*;
			if (token_text === '*' && last_type === 'TK_DOT' && !last_last_text.match(/^\d+$/)) {
				print_token();
				return;
			}

			if (token_text === ':' && flags.in_case) {
				flags.case_body = true;
				indent();
				print_token();
				print_newline();
				flags.in_case = false;
				return;
			}

			if (token_text === '::') {
				// no spaces around exotic namespacing syntax operator
				print_token();
				return;
			}

			// http://www.ecma-international.org/ecma-262/5.1/#sec-7.9.1
			// if there is a newline between -- or ++ and anything else we should preserve it.
			if (input_wanted_newline && (token_text === '--' || token_text === '++')) {
				print_newline();
			}

			if (in_array(token_text, ['--', '++', '!']) || (in_array(token_text, ['-', '+']) && (in_array(last_type, ['TK_START_BLOCK', 'TK_START_EXPR', 'TK_EQUALS', 'TK_OPERATOR']) || in_array(flags.last_text, line_starters) || flags.last_text === ','))) {
				// unary operators (and binary +/- pretending to be unary) special cases

				space_before = false;
				space_after = false;

				if (flags.last_text === ';' && is_expression(flags.mode)) {
					// for (;; ++i)
					//        ^^^
					space_before = true;
				}

				if (last_type === 'TK_WORD' && in_array(flags.last_text, line_starters)) {
					space_before = true;
				}

				if ((flags.mode === MODE.BlockStatement || flags.mode === MODE.Statement) && (flags.last_text === '{' || flags.last_text === ';')) {
					// { foo; --i }
					// foo(); --bar;
					print_newline();
				}
			} else if (token_text === ':') {
				if (flags.ternary_depth === 0) {
					if (flags.mode === MODE.BlockStatement) {
						flags.mode = MODE.ObjectLiteral;
					}
					space_before = false;
				} else {
					flags.ternary_depth -= 1;
				}
			} else if (token_text === '?') {
				flags.ternary_depth += 1;
			}
			output_space_before_token = output_space_before_token || space_before;
			print_token();
			output_space_before_token = space_after;
		}

		function handle_block_comment() {
			var lines = split_newlines(token_text);
			var j; // iterator for this case
			var javadoc = false;

			// block comment starts with a new line
			print_newline(false, true);
			if (lines.length > 1) {
				if (all_lines_start_with(lines.slice(1), '*')) {
					javadoc = true;
				}
			}

			// first line always indented
			print_token(lines[0]);
			for (j = 1; j < lines.length; j++) {
				print_newline(false, true);
				if (javadoc) {
					// javadoc: reformat and re-indent
					print_token(' ' + trim(lines[j]));
				} else {
					// normal comments output raw
					output_lines[output_lines.length - 1].text.push(lines[j]);
				}
			}

			// for comments of more than one line, make sure there's a new line after
			print_newline(false, true);
		}

		function handle_inline_comment() {
			output_space_before_token = true;
			print_token();
			output_space_before_token = true;
		}

		function handle_comment() {
			if (input_wanted_newline) {
				print_newline(false, true);
			} else {
				trim_output(true);
			}

			output_space_before_token = true;
			print_token();
			print_newline(false, true);
		}

		function handle_dot() {
			if (is_special_word(flags.last_text)) {
				output_space_before_token = true;
			} else {
				// allow preserved newlines before dots in general
				// force newlines on dots after close paren when break_chained - for bar().baz()
				allow_wrap_or_preserved_newline(flags.last_text === ')' && opt.break_chained_methods);
			}

			print_token();
		}

		function handle_unknown() {
			print_token();

			if (token_text[token_text.length - 1] === '\n') {
				print_newline();
			}
		}
	}
};